﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using SecurityFoundation;
using SecurityFoundation.Entities;
using System.Runtime.Serialization;
using System.ServiceModel;


namespace SecurityAgent
{

    [SecurityExceptionHandler(AttributePriority = 99)]
    public class ObjectSecurityService : SecurityAgent.IObjectSecurityService
    {
        public bool IsObjectAuthorized(string applicationKey, string userToken, string objectName, int operation)
        {
            int level = GetObjectAuthorization(applicationKey, userToken, objectName);



            int result = level & operation;
            return result > 0;
        }

        public int GetObjectAuthorization(string applicationKey, string userToken, string objectName)
        {
            int level = 0;

            try
            {
                String userName = Repository.ValidUserToken(new Guid(applicationKey), userToken);

                ApplicationDefination app = Repository.GetApplicationDefinition(new Guid(applicationKey));

                if (app.ObjectSecurity == (int)ObjectSecurityRequirement.NotRequired) return 15;    // 0x1111

                ObjectDefinition obj = Repository.GetObjectDefinition(app.ApplicationKey, objectName);

                if (obj == null)
                {
                    if (app.ObjectSecurity == (int)ObjectSecurityRequirement.Required)
                    {
                        return 0;
                    }
                    else
                    {
                        return 1;
                    }
                }

                RoleProviderService roleService = new RoleProviderService();

                bool bIsOwned = false;
                if (!String.IsNullOrEmpty(obj.OwnerRole) && roleService.IsUserInRole(applicationKey, userToken, String.Empty, obj.OwnerRole))
                {
                    bIsOwned = true;
                }

                ObjectAcl[] acls = Repository.GetObjectAcl(app.ApplicationKey, objectName);

                List<ObjectAcl> validAcls = new List<ObjectAcl>();

                foreach (ObjectAcl acl in acls)
                {
                    if (roleService.IsUserInRole(applicationKey, userToken, String.Empty, acl.RoleName))
                    {
                        validAcls.Add(acl);
                    }
                }

                if (app.ObjectSecurity == (int)ObjectSecurityRequirement.Optional)
                {
                    level = (int)SecurityAccessMode.AllowAll;         // allowed by default

                    if (validAcls.Count > 0)
                    {
                        foreach (ObjectAcl acl in validAcls)
                        {
                            if (bIsOwned)
                            {
                                if (!acl.OwnAllowRead) level = level & (int)SecurityAccessMode.DenyRead;
                                if (!acl.OwnAllowCreate) level = level & (int)SecurityAccessMode.DenyCreate;
                                if (!acl.OwnAllowUpdate) level = level & (int)SecurityAccessMode.DenyUpdate;
                                if (!acl.OwnAllowDelete) level = level & (int)SecurityAccessMode.DenyDelete;
                            }
                            else
                            {
                                if (!acl.AllAllowRead) level = level & (int)SecurityAccessMode.DenyRead;
                                if (!acl.AllAllowCreate) level = level & (int)SecurityAccessMode.DenyCreate;
                                if (!acl.AllAllowUpdate) level = level & (int)SecurityAccessMode.DenyUpdate;
                                if (!acl.AllAllowDelete) level = level & (int)SecurityAccessMode.DenyDelete;
                            }
                        }
                    }
                }
                else    // SecurityRequirement.Required
                {
                    level = (int)SecurityAccessMode.DenyAll;

                    if (validAcls.Count > 0)
                    {
                        foreach (ObjectAcl acl in validAcls)
                        {
                            if (bIsOwned)
                            {
                                if (acl.OwnAllowRead) level = level | (int)SecurityAccessMode.AllowRead;
                                if (acl.OwnAllowCreate) level = level | (int)SecurityAccessMode.AllowCreate;
                                if (acl.OwnAllowUpdate) level = level | (int)SecurityAccessMode.AllowUpdate;
                                if (acl.OwnAllowDelete) level = level | (int)SecurityAccessMode.AllowDelete;
                            }
                            else
                            {
                                if (acl.AllAllowRead) level = level | (int)SecurityAccessMode.AllowRead;
                                if (acl.AllAllowCreate) level = level | (int)SecurityAccessMode.AllowCreate;
                                if (acl.AllAllowUpdate) level = level | (int)SecurityAccessMode.AllowUpdate;
                                if (acl.AllAllowDelete) level = level | (int)SecurityAccessMode.AllowDelete;
                            }
                        }
                    }
                }

                //ObjectClaim ret = new ObjectClaim();
                //ret.ObjectKey = obj.ObjectId;
                //ret.ObjectName = obj.ObjectName;
                //ret.AccessLevel = new ObjectAccess();
                //ret.AccessLevel.AllowRead = ((level & (int)SecurityAccessMode.AllowRead) > 0);
                //ret.AccessLevel.AllowCreate = ((level & (int)SecurityAccessMode.AllowCreate) > 0);
                //ret.AccessLevel.AllowDelete  = ((level & (int)SecurityAccessMode.AllowDelete) > 0);
                //ret.AccessLevel.AllowUpdate = ((level & (int)SecurityAccessMode.AllowUpdate) > 0);

                //return ret;

                return level;
            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }


        }

        public ObjectClaim GetObjectClaim(string applicationKey, string userToken, string objectName, bool requireFieldLevel)
        {
            try
            {
                String userName = Repository.ValidUserToken(new Guid(applicationKey), userToken);

                ApplicationDefination app = Repository.GetApplicationDefinition(new Guid(applicationKey));

                ObjectDefinition obj = Repository.GetObjectDefinition(app.ApplicationKey, objectName);

                int level = 0;

                if (obj == null)
                {
                    /// Added this code to handle the situation that an invalid object is provided and the behavior is controlled by the objectsecuritymodel attribute

                    if (app.ObjectSecurity == (int)ObjectSecurityRequirement.Required)
                    {
                        throw new FaultException<UnauthorizedAccessFault>(new UnauthorizedAccessFault { Application = applicationKey, Object = objectName, Operation = "Access" }, new FaultReason("No authorization record found"));
                    }
                    else
                    {
                        ObjectClaim ret1 = new ObjectClaim();
                        ret1.ObjectName = objectName;
                        ret1.ObjectCategory = null;
                        ret1.FieldLevelSecurityEnabled = null;
                        ret1.ObjectAccess = new AccessLevel();
                        ret1.ObjectAccess.AllowRead = true;
                        ret1.ObjectAccess.AllowCreate = true;
                        ret1.ObjectAccess.AllowDelete = true;
                        ret1.ObjectAccess.AllowUpdate = true;

                        return ret1;
                    }
                }

                if (app.ObjectSecurity == (int)ObjectSecurityRequirement.NotRequired)
                {
                    level = (int)SecurityAccessMode.AllowAll;
                }
                else
                {
                    level = GetObjectAuthorization(applicationKey, userToken, objectName);
                }



                ObjectClaim ret = new ObjectClaim();
                ret.ObjectKey = obj.ObjectId;
                ret.ObjectName = obj.ObjectName;
                ret.ObjectCategory = obj.ObjectCategory;
                ret.FieldLevelSecurityEnabled = Enum.GetName(typeof(FieldSecurityRequirement), (FieldSecurityRequirement)obj.FieldLevelSecurity);
                ret.ObjectAccess = new AccessLevel();
                ret.ObjectAccess.ObjectKey = obj.ObjectId;
                ret.ObjectAccess.AllowRead = ((level & (int)SecurityAccessMode.AllowRead) > 0);
                ret.ObjectAccess.AllowCreate = ((level & (int)SecurityAccessMode.AllowCreate) > 0);
                ret.ObjectAccess.AllowDelete = ((level & (int)SecurityAccessMode.AllowDelete) > 0);
                ret.ObjectAccess.AllowUpdate = ((level & (int)SecurityAccessMode.AllowUpdate) > 0);

                if (requireFieldLevel)
                {
                    FieldSecurityService fieldService = new FieldSecurityService();

                    FieldDefinition[] fields = Repository.GetObjectFields(app.ApplicationKey, objectName);

                    if (fields != null)
                    {
                        ret.Fields = new FieldClaimCollection();

                        foreach (FieldDefinition fd in fields)
                        {
                            ret.Fields.Add(fieldService.GetFieldClaim(applicationKey, userToken, obj.ObjectName, fd.FieldName));
                        }
                    }
                }

                return ret;
            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }

        }

        //[RequireAdminAccess(AttributePriority = 10)]
        public ObjectClaim[] GetAllObjects(string applicationKey, string userToken, bool requireFieldDetail)
        {
            ApplicationDefination app = Repository.GetApplicationDefinition(new Guid(applicationKey), true);

            if (app.ObjectDefinitionCollection == null || app.ObjectDefinitionCollection.Count == 0)
            {
                return null;
            }

            ObjectClaim[] claims = new ObjectClaim[app.ObjectDefinitionCollection.Count];

            int i = 0;
            foreach (ObjectDefinition obj in app.ObjectDefinitionCollection)
            {
                claims[i] = GetObjectClaim(applicationKey, userToken, obj.ObjectName, requireFieldDetail);
                i++;
            }

            return claims;
        }

        //[RequireAdminAccess(AttributePriority = 10)]
        public ObjectClaim[] GetObjectsByCategory(string applicationKey, string userToken, string category, bool requireFieldDetail)
        {
            List<ObjectClaim> ret = new List<ObjectClaim>();
            ApplicationDefination app = Repository.GetApplicationDefinition(new Guid(applicationKey), false);

            if (app.ObjectDefinitionCollection != null && app.ObjectDefinitionCollection.Count > 0)
            {

                foreach (ObjectDefinition obj in app.ObjectDefinitionCollection)
                {
                    if (obj.ObjectCategory == category)
                    {
                        ObjectClaim oc = GetObjectClaim(applicationKey, userToken, obj.ObjectName, requireFieldDetail);
                        if (oc != null) ret.Add(oc);
                    }
                }

                if (ret.Count > 0)
                    return ret.ToArray();

            }
            else
            {
                ObjectDefinition[] oblist = Repository.GetApplicationObjects(app.ApplicationKey, category);
                if (oblist != null)
                {
                    foreach (ObjectDefinition obj in oblist)
                    {
                        ObjectClaim oc = GetObjectClaim(applicationKey, userToken, obj.ObjectName, requireFieldDetail);
                        if (oc != null) ret.Add(oc);
                    }
                }

                if (ret.Count > 0)
                    return ret.ToArray();
            }



            return null;
        }

        #region IObjectSecurityService Members


        public string GetObjectSettingValue(string applicationKey, string userToken, string objectName, string setting)
        {
            if (!IsObjectAuthorized(applicationKey, userToken, objectName, (int)SecurityAccessMode.AllowRead))
            {
                throw new FaultException<UnauthorizedAccessFault>(new UnauthorizedAccessFault { Application = applicationKey, Object = objectName, Operation = "Read Settings" }, new FaultReason("Unauthorized access attempt recorded"));
            }


            try
            {
                UserProfileData profile = Repository.GetUserProfileData(new Guid(applicationKey), "O_" + objectName.Trim());
                string ret = string.Empty;
                if (profile == null) return null;

                XDocument xdoc = XDocument.Parse(profile.ProfileData);

                var query = (from en in xdoc.Root.Elements(setting)
                             select en).FirstOrDefault();

                if (query != null)
                {
                    ret = query.Value;
                }

                return ret;
            }
            catch (Exception ee)
            {
                throw new FaultException(ee.Message);
            }

        }

        public bool SetObjectSetting(string applicationKey, string userToken, string objectName, string setting, string value)
        {
            string user = string.Empty;

            if (String.IsNullOrEmpty(objectName))
            {
                throw new FaultException("Invalid parameter");
            }

            user = "O_" + objectName;

            UserProfileData profile = Repository.GetUserProfileData(new Guid(applicationKey), user);

            if (profile == null)
            {
                profile = new UserProfileData();
                profile.ProfileDataId = 0;
                profile.UserName = user;
                profile.ApplicationKey = new Guid(applicationKey);
                profile.LastUpdated = DateTime.Now;
            }

            try
            {
                XDocument xdoc;

                if (String.IsNullOrEmpty(profile.ProfileData))
                {
                    xdoc = new XDocument
                        (
                            new XDeclaration("1.0", "utf-8", null)
                            , new XElement("Profile",
                                new XElement("UserName", user),
                                new XElement("ApplicationKey", applicationKey)
                                )
                        );
                }
                else
                {
                    xdoc = XDocument.Parse(profile.ProfileData);
                }

                var query = (from en in xdoc.Root.Elements(setting)
                             select en).FirstOrDefault();

                if (query != null)
                {
                    query.Value = value;
                }
                else
                {
                    xdoc.Root.Add(new XElement(setting, value));
                }

                profile.ProfileData = xdoc.ToString();

            }
            catch (Exception ee)
            {
                throw new FaultException(ee.Message);
            }

            if (profile.ProfileDataId == 0)
            {
                Repository.InsertProfileData(profile);
            }
            else
            {
                Repository.UpdateProfileData(profile);
            }

            return true;
        }


        public string GetObjectSettings(string applicationKey, string userToken, string objectName)
        {
            if (!IsObjectAuthorized(applicationKey, userToken, objectName, (int)SecurityAccessMode.AllowRead))
            {
                throw new FaultException<UnauthorizedAccessFault>(new UnauthorizedAccessFault { Application = applicationKey, Object = objectName, Operation = "Read Settings" }, new FaultReason("Unauthorized access attempt recorded"));
            }

            try
            {

                UserProfileData profile = Repository.GetUserProfileData(new Guid(applicationKey), "O_" + objectName.Trim());
                string ret = string.Empty;
                if (profile == null) return null;
                else
                    return profile.ProfileData;
            }
            catch (Exception ee)
            {
                throw new FaultException(ee.Message);
            }

        }

        #endregion
    }
}
